// 1. 包声明：指定当前类所属包路径，遵循"项目域+模块"规范，web包专门存放SSM框架的控制器（Controller）
package cn.edu.buu.hospital.web;

// 2. 导入依赖类：引入当前控制器所需的实体、服务、工具类等
// 导入医生实体类：封装医生的属性（如医生ID、姓名、所属科室/医院等），用于前后端数据传输
import cn.edu.buu.hospital.entity.Doctor;
// 导入科室服务接口：用于获取科室列表（医生关联科室，新增/编辑时需下拉选择）
import cn.edu.buu.hospital.service.DepartmentService;
// 导入医生服务接口：定义医生相关业务逻辑（增删改查），控制器通过它调用业务层
import cn.edu.buu.hospital.service.DoctorService;
// 导入医院服务接口：用于获取医院列表（医生关联医院，新增/编辑时需下拉选择）
import cn.edu.buu.hospital.service.HospitalService;
// 导入排班服务接口：用于处理医生关联的排班业务（如删除医生前检查排班关联）
import cn.edu.buu.hospital.service.VisitService;
// 导入分页插件结果封装类：存储分页查询的元数据（总条数、当前页数据、总页数等）
import com.github.pagehelper.PageInfo;
// 导入Servlet HTTP请求对象：用于获取请求信息、操作Session等
import jakarta.servlet.http.HttpServletRequest;
// 导入Servlet HTTP会话对象：存储跨请求临时数据（如错误信息、表单回显数据）
import jakarta.servlet.http.HttpSession;
// 导入Spring依赖注入注解：自动从IOC容器装配Service层Bean，解耦层间依赖
import org.springframework.beans.factory.annotation.Autowired;
// 导入Spring MVC控制器注解：标记当前类为控制器，纳入Spring IOC容器管理
import org.springframework.stereotype.Controller;
// 导入Spring事务注解：声明方法需事务支持（保证数据操作原子性，如新增医生失败回滚）
import org.springframework.transaction.annotation.Transactional;
// 导入Spring MVC模型对象：用于向前端视图传递动态数据（如列表、提示信息）
import org.springframework.ui.Model;
// 导入Spring MVC请求映射注解：处理HTTP请求与方法的绑定
import org.springframework.web.bind.annotation.*;
// 导入Spring请求上下文工具类：在非方法参数注入场景下，获取当前HTTP请求对象
import org.springframework.web.context.request.RequestContextHolder;
// 导入Spring请求上下文属性类：配合RequestContextHolder获取HttpServletRequest
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 医生管理控制类
 * 负责处理医生相关的HTTP请求，包括新增医生、查询医生、更新医生、删除医生等功能
 */
// 3. 类注解与声明：标记为Spring MVC控制器，统一映射医生相关URL
@Controller
// 类级请求映射：当前类所有方法的URL都以"/doctor"开头（如"/doctor/add""/doctor/find"）
@RequestMapping("/doctor")
public class DoctorController {

    // 4. 依赖注入：通过@Autowired自动装配Service层Bean，无需手动创建对象
    // 注入医生服务：核心业务逻辑入口，处理医生的增删改查、业务校验等
    @Autowired
    private DoctorService doctorService; // 注入医生服务，处理医生相关的业务逻辑

    // 注入科室服务：用于查询所有科室，供医生新增/编辑时的"所属科室"下拉选择
    @Autowired
    private DepartmentService departmentService; // 注入科室服务，用于获取科室信息

    // 注入医院服务：用于查询所有医院，供医生新增/编辑时的"所属医院"下拉选择
    @Autowired
    private HospitalService hospitalService; // 注入医院服务，用于获取医院信息

    // 注入排班服务：用于处理医生关联的排班业务（如删除医生前检查是否有未删除的排班）
    @Autowired
    private VisitService visitService; // 注入排班服务，用于处理医生相关的排班信息

    /**
     * 跳转到新增医生页面
     * @param model 模型对象：向前端新增页面传递动态数据（如科室列表、医院列表）
     * @return 新增医生页面的视图路径（由视图解析器定位到具体页面）
     */
    // 5. 方法：跳转到新增医生页面（处理GET请求）
    // GET请求映射：URL为"/doctor/toAdd"，前端点击"新增医生"按钮时触发
    @GetMapping("/toAdd")
    public String toAdd(Model model) {
        // 向视图传递科室列表：调用科室服务查询所有科室，供新增页下拉选择
        model.addAttribute("departmentList", departmentService.findAll());
        // 向视图传递医院列表：调用医院服务查询所有医院，供新增页下拉选择
        model.addAttribute("hospitalList", hospitalService.findAll());
        // 返回新增医生页面的视图路径：视图解析器会定位到"backend/doctor/add.html"（如Thymeleaf模板）
        return "backend/doctor/add"; // 返回新增医生页面
    }

    /**
     * 处理新增医生表单提交
     * @param doctor 医生实体对象：Spring MVC自动将表单参数绑定到该对象（属性名与表单字段名一致）
     * @param model 模型对象：理论上可传数据，但因重定向会丢失，实际用Session存临时数据
     * @return 成功→重定向到医生列表页；失败→重定向到列表页（带错误信息）
     */
    // 6. 方法：新增医生（处理POST请求）
    // POST请求映射：URL为"/doctor/add"，新增页表单提交时触发
    @PostMapping("/add")
    // 事务注解：声明该方法需事务支持，若新增过程抛异常，数据库操作会回滚（避免脏数据）
    @Transactional
    public String add(Doctor doctor, Model model) {
        try {
            // 调用医生服务保存医生信息：业务逻辑由Service层处理（如数据校验、数据库插入）
            doctorService.add(doctor);
            // 新增成功：重定向到医生列表页（用"redirect:"避免表单重复提交，刷新页面不会重新提交）
            return "redirect:find";
        } catch (Exception e) {
            // 新增失败：捕获异常（业务异常如"医生工号重复"、系统异常如"数据库连接失败"）
            // 获取当前HTTP请求对象：通过RequestContextHolder工具类，非方法参数注入场景下获取request
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            // 保存错误信息到Session：重定向会丢失Model数据，需用Session跨请求传递
            request.getSession().setAttribute("addError", "新增失败: " + e.getMessage());
            // 保存用户输入的医生数据到Session：用于列表页回显（避免用户重新输入）
            request.getSession().setAttribute("addDoctor", doctor);
            // 重定向到医生列表页：在列表页显示错误信息和回显数据
            return "redirect:find";
        }
    }

    /**
     * 分页查询医生列表（支持直接访问列表、条件查询、编辑回显）
     * @param model 模型对象：向前端列表页传递分页数据、错误信息、下拉列表等
     * @param pageNum 当前页码：从URL获取，默认值1（首次访问显示第1页）
     * @param editId 编辑的医生ID：可选参数，用于定位要编辑的医生
     * @param edit 是否处于编辑状态：可选参数，标记列表页需显示编辑模态框
     * @param condition 查询条件对象：Spring MVC自动绑定表单查询参数（如医生姓名、所属科室）
     * @return 医生列表页面的视图路径
     */
    // 7. 方法：分页查询医生列表（支持GET/POST请求）
    // 请求映射：URL为"/doctor/find"，支持GET（直接访问列表）和POST（提交查询条件）
    @RequestMapping(value = "/find", method = {RequestMethod.GET, RequestMethod.POST})
    public String find(Model model,
                       // 绑定URL参数"pageNum"，默认值1（用户未传页码时显示第1页）
                       @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
                       // 绑定编辑的医生ID（可选，前端点击"编辑"时传递）
                       @RequestParam(value = "editId", required = false) Long editId,
                       // 绑定编辑状态标记（可选，标记列表页需显示编辑模态框）
                       @RequestParam(value = "edit", required = false) Boolean edit,
                       // 绑定查询条件：前端表单提交的"医生姓名""所属科室"等参数自动封装到该对象
                       Doctor condition) {
        // 设置每页显示条数：固定为5条（可根据需求改为参数传递）
        int pageSize = 5;
        // 初始化查询条件：若condition为null（无查询参数时），新建空对象避免空指针
        if (condition == null) {
            condition = new Doctor();
        }
        // 调用医生服务分页查询：传入页码、每页条数、查询条件，返回分页结果PageInfo
        PageInfo<Doctor> pageInfo = doctorService.find(pageNum, pageSize, condition);
        // 向视图传递分页数据：包含当前页医生列表、总条数、总页数等
        model.addAttribute("pageInfo", pageInfo);
        // 向视图传递查询条件：回显到列表页查询表单，方便用户再次调整查询条件
        model.addAttribute("condition", condition);

        // 获取当前HTTP请求和Session对象：用于读取之前保存的编辑数据、错误信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();

        // 处理编辑数据回显：优先读取Session中保存的编辑数据（如修改失败时的临时数据）
        Doctor sessionDoctor = (Doctor) session.getAttribute("doctor");
        if (sessionDoctor != null) {
            // 传递Session中的编辑数据到视图：列表页编辑模态框回显
            model.addAttribute("doctor", sessionDoctor);
            // 移除Session中的数据：避免下次访问列表页重复回显
            session.removeAttribute("doctor");
        } else if (editId != null || Boolean.TRUE.equals(edit)) {
            // 无Session数据：若有editId或edit=true，根据ID查询医生信息回显
            if (editId != null) {
                // 调用医生服务根据ID查询医生
                Doctor doctor = doctorService.findById(editId);
                // 若查询结果为null（ID不存在），新建对象并设置ID：避免前端空指针
                if (doctor == null) {
                    doctor = new Doctor();
                    doctor.setDoctorId(editId);
                }
                // 传递查询到的医生数据到视图：编辑模态框回显
                model.addAttribute("doctor", doctor);
            }
        }

        // 处理错误信息回显：从Session读取并传递到视图，显示后移除Session数据
        // 1. 读取修改失败的错误信息
        String modifyError = (String) session.getAttribute("modifyError");
        if (modifyError != null) {
            model.addAttribute("error", modifyError);
            session.removeAttribute("modifyError");
        }
        // 2. 读取新增失败的错误信息
        String addError = (String) session.getAttribute("addError");
        if (addError != null) {
            model.addAttribute("error", addError);
            session.removeAttribute("addError");
        }
        // 3. 读取其他通用错误信息（如删除失败）
        String error = (String) session.getAttribute("error");
        if (error != null) {
            model.addAttribute("error", error);
            session.removeAttribute("error");
        }

        // 向视图传递科室/医院列表：供查询表单的下拉框使用
        model.addAttribute("departmentList", departmentService.findAll());
        model.addAttribute("hospitalList", hospitalService.findAll());

        // 返回医生列表页面视图路径：视图解析器定位到"backend/doctor/list.html"
        return "backend/doctor/list";
    }

    /**
     * 根据医生ID查询详情（用于编辑模态框回显）
     * @param doctorId 医生ID：从URL获取，前端点击"编辑"按钮时传递
     * @param session HTTP会话对象：存储查询到的医生数据，供列表页回显
     * @return 重定向到医生列表页，并携带"edit=true"标记（触发编辑模态框显示）
     */
    // 8. 方法：根据ID查询医生（用于编辑，处理GET请求）
    // GET请求映射：URL为"/doctor/findById?doctorId=xxx"，前端编辑按钮触发
    @GetMapping("/findById")
    public String findById(@RequestParam("doctorId") Long doctorId, HttpSession session) {
        // 调用医生服务根据ID查询医生详情
        Doctor doctor = doctorService.findById(doctorId);
        // 若查询结果为null（ID不存在），新建对象并设置ID：避免前端空指针
        if (doctor == null) {
            doctor = new Doctor();
            doctor.setDoctorId(doctorId);
        }
        // 保存医生数据到Session：因重定向会丢失Model数据，用Session传递到列表页
        session.setAttribute("doctor", doctor);
        // 重定向到列表页，并携带"edit=true"：告诉列表页需要显示编辑模态框
        return "redirect:find?edit=true";
    }

    /**
     * 处理医生信息修改提交
     * @param doctor 包含更新信息的医生实体：表单提交的新数据自动绑定到该对象
     * @param session HTTP会话对象：存储错误信息和医生数据（修改失败时回显）
     * @return 成功→重定向到列表页；失败→重定向到列表页（带editId，触发编辑回显）
     */
    // 9. 方法：修改医生信息（处理POST请求）
    // POST请求映射：URL为"/doctor/modify"，编辑模态框表单提交时触发
    @PostMapping("modify")
    public String modify(Doctor doctor, HttpSession session) {
        // 打印调试日志：记录接收到的编辑请求（便于排查问题）
        System.out.println("接收到医生编辑请求，医生ID: " + doctor.getDoctorId());
        try {
            // 调用医生服务修改医生信息：业务逻辑由Service层处理（如数据校验、数据库更新）
            doctorService.modify(doctor);
            // 打印调试日志：记录修改成功
            System.out.println("医生编辑成功，医生ID: " + doctor.getDoctorId());
            // 修改成功：重定向到列表页（避免表单重复提交）
            return "redirect:find";
        } catch (RuntimeException e) {
            // 捕获业务异常（如Service层抛出的"医生工号重复""科室不存在"等）
            System.err.println("医生编辑业务异常: " + e.getMessage()); // 打印错误日志
            e.printStackTrace(); // 打印异常堆栈，便于定位问题
            // 保存错误信息到Session：供列表页显示
            session.setAttribute("modifyError", e.getMessage());
            // 保存用户输入的医生数据到Session：用于编辑模态框回显
            session.setAttribute("doctor", doctor);
            // 重定向到列表页，并携带editId：触发编辑模态框显示，回显错误和数据
            return "redirect:find?editId=" + doctor.getDoctorId();
        } catch (Exception e) {
            // 捕获系统异常（如数据库连接失败、SQL错误等）
            System.err.println("医生编辑系统异常: " + e.getMessage()); // 打印错误日志
            e.printStackTrace(); // 打印异常堆栈
            // 保存通用错误信息到Session：避免暴露系统细节给用户
            session.setAttribute("modifyError", "系统错误，请稍后重试");
            // 保存用户输入的医生数据到Session：用于编辑模态框回显
            session.setAttribute("doctor", doctor);
            // 重定向到列表页，并携带editId：触发编辑模态框显示
            return "redirect:find?editId=" + doctor.getDoctorId();
        }
    }

    /**
     * 根据医生ID删除医生
     * @param doctorId 医生ID：从URL获取，前端点击"删除"按钮时传递
     * @return 无论成功失败，均重定向到医生列表页（通过Session显示结果信息）
     */
    // 10. 方法：删除医生（处理GET请求）
    // GET请求映射：URL为"/doctor/removeById?doctorId=xxx"，前端删除按钮触发
    @GetMapping("/removeById")
    // 事务注解：声明该方法需事务支持，若删除失败（如关联数据存在），回滚操作
    @Transactional
    public String removeById(@RequestParam("doctorId") Long doctorId) {
        try {
            // 先查询医生信息：确认医生存在，且可后续扩展"检查关联排班"逻辑
            Doctor doctor = doctorService.findById(doctorId);
            if (doctor != null) {
                // 注释说明：可扩展检查逻辑（如判断该医生是否有未删除的排班，有则抛异常）
                // 这里可以添加检查逻辑，例如检查是否有关联的排班
                // 由于系统设计可能不同，暂时先直接删除
                // 调用医生服务删除医生
                doctorService.removeById(doctorId);
            }
            // 删除成功：重定向到列表页
            return "redirect:find";
        } catch (Exception e) {
            // 删除失败：捕获异常（如业务异常"医生有关联排班"、系统异常"数据库错误"）
            // 获取当前HTTP请求对象：用于操作Session
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            // 保存错误信息到Session：供列表页显示
            request.getSession().setAttribute("error", "删除失败: " + e.getMessage());
            // 重定向到列表页：显示错误信息
            return "redirect:find";
        }
    }
}